﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RoketPack.Collections;
using System.Drawing;
using System.IO;

namespace RoketPack.Management
{
    /// <summary>
    /// This class represents a downloadable application and it's components.
    /// </summary>
    class Application : MarshalByRefObject
    {
        private string p_ID;
        private Repository p_Repository;
        private string p_Name;
        private string p_XmlSource;
        private Tree p_XmlTree = null;
        private Dictionary<string, Component> p_Components = new Dictionary<string, Component>();
        private string p_Description;
        private bool p_IsAvailable = true;
        private bool p_IsValid = true;
        private string p_Category;
        private Bitmap p_Image = null;
        private string p_ImageSource = null;
        private Installation p_InstallationData = null;
        private string p_LaunchPath = null;
        private string p_LaunchArgs = null;

        private bool p_PermitModification = false;

        private Application()
        {
        }

        /// <summary>
        /// Fetchs an Application instance based on data that's currently located
        /// in the cache.
        /// </summary>
        /// <param name="repo">The repository URL.</param>
        /// <param name="id">The ID of the application within the repository.</param>
        /// <returns>An application instance, or null if no application could be found.</returns>
        internal static Application FetchFromCache(Repository repo, string id)
        {
            return Application.FetchFromCache(repo, id, null);
        }

        /// <summary>
        /// Fetchs an Application instance based on data that's currently located
        /// in the cache.
        /// </summary>
        /// <param name="repo">The repository URL.</param>
        /// <param name="id">The ID of the application within the repository.</param>
        /// <param name="srcurl">The URL to retrieve the application XML document from if it's not in the cache.</param>
        /// <returns>An application instance, or null if no application could be found.</returns>
        internal static Application FetchFromCache(Repository repo, string id, string srcurl)
        {
            byte[] xmldata = Cache.GetApplicationData(repo.Url, id, srcurl);
            ConfigurationReader config = new ConfigurationReader();
            return config.GetApplicationData(xmldata, repo, srcurl);
        }

        /// <summary>
        /// Create a new instance of Application, with no settings applied.  The data in this
        /// application can be modified via properties until PermitModification is set to true.
        /// </summary>
        /// <returns>The new Application instance.</returns>
        internal static Application FetchEmpty()
        {
            Application a = new Application();
            a.p_PermitModification = true;
            return a;
        }

        /// <summary>
        /// Syncronizes the cache with the appropriate installation information.  In particular
        /// this function is used to update the 'instversion' XML element which is only found
        /// in cached objects.
        /// </summary>
        internal void SyncCache()
        {
            // Check to see whether we should add instversion.
            if (this.IsInstalled())
            {
                // Calculate which components are installed and maintain references
                // to them.
                Dictionary<string, Component> installed = new Dictionary<string, Component>();
                foreach (Component c in this.p_Components.Values)
                {
                    if (c.IsInstalled())
                        installed.Add(c.ID, c);
                }

                // Search through the component nodes.
                foreach (Node n in this.XmlTree.GetChildElement("application").GetChildElements("component"))
                {
                    try
                    {
                        Node i = n.GetChildElement("installation");
                        Component c = installed[n.GetChildElement("id").GetText().Value];

                        // Search through all of the file nodes.
                        foreach (Node nc in i.GetChildElements("file"))
                        {
                            // Then search through all of the File objects.
                            foreach (Objects.File f in c.Files)
                            {
                                // .. and see if we find a match based on values.
                                if (f.Local == nc.GetChildElement("local").GetText().Value &&
                                    (f.Generated || f.Remote == nc.GetChildElement("remote").GetText().Value))
                                {
                                    // We have, so check to see whether an instversion attribute
                                    // already exist.
                                    if (!nc.Attributes.Keys.Contains("instversion"))
                                    {
                                        // Force the version to the latest,
                                        // and add the instversion attribute.
                                        if (f.InstalledVersion == -1)
                                        {
                                            c.ForceInstalledVersion(f, f.AvailableVersion);
                                            nc.Attributes.Add("instversion", f.AvailableVersion.ToString());
                                        }
                                        else
                                        {
                                            c.ForceInstalledVersion(f, f.InstalledVersion);
                                            nc.Attributes.Add("instversion", f.InstalledVersion.ToString());
                                        }
                                    }
                                    else
                                    {
                                        // Ensure the value of instversion matches
                                        // the InstalledVersion property.
                                        nc.Attributes["instversion"] = f.InstalledVersion.ToString();
                                    }

                                    // Ensure a copy of the version information is made in the backup.
                                    RecoveryUtil.SaveVersionInformationForObject(this.Repository.Url, this.ID, c.ID, "file", f.Local, f.InstalledVersion);
                                }
                            }
                        }
                        
                        // Repeat the process for folders.
                        foreach (Node nc in i.GetChildElements("folder"))
                        {
                            foreach (Objects.Folder f in c.Folders)
                            {
                                if (f.Local == nc.GetChildElement("local").GetText().Value)
                                {
                                    if (!nc.Attributes.Keys.Contains("instversion"))
                                    {
                                        if (f.InstalledVersion == -1)
                                        {
                                            c.ForceInstalledVersion(f, f.AvailableVersion);
                                            nc.Attributes.Add("instversion", f.AvailableVersion.ToString());
                                        }
                                        else
                                        {
                                            c.ForceInstalledVersion(f, f.InstalledVersion);
                                            nc.Attributes.Add("instversion", f.InstalledVersion.ToString());
                                        }
                                    }
                                    else
                                    {
                                        nc.Attributes["instversion"] = f.InstalledVersion.ToString();
                                    }

                                    // Ensure a copy of the version information is made in the backup.
                                    RecoveryUtil.SaveVersionInformationForObject(this.Repository.Url, this.ID, c.ID, "folder", f.Local, f.InstalledVersion);
                                }
                            }
                        }

                        // Repeat the process for links.
                        foreach (Node nc in i.GetChildElements("link"))
                        {
                            foreach (Objects.Link l in c.Links)
                            {
                                // Normalize the link.
                                string linkpath = nc.GetChildElement("local").GetText().Value;
                                if (!linkpath.ToLowerInvariant().EndsWith(".lnk")) linkpath = linkpath + ".lnk";

                                if (l.Local == linkpath)
                                {
                                    if (!nc.Attributes.Keys.Contains("instversion"))
                                    {
                                        if (l.InstalledVersion == -1)
                                        {
                                            c.ForceInstalledVersion(l, l.AvailableVersion);
                                            nc.Attributes.Add("instversion", l.AvailableVersion.ToString());
                                        }
                                        else
                                        {
                                            c.ForceInstalledVersion(l, l.InstalledVersion);
                                            nc.Attributes.Add("instversion", l.InstalledVersion.ToString());
                                        }
                                    }
                                    else
                                    {
                                        nc.Attributes["instversion"] = l.InstalledVersion.ToString();
                                    }

                                    // Ensure a copy of the version information is made in the backup.
                                    RecoveryUtil.SaveVersionInformationForObject(this.Repository.Url, this.ID, c.ID, "link", nc.GetChildElement("local").GetText().Value, l.InstalledVersion);
                                }
                            }
                        }

                        // Repeat the process for the classic installer.
                        if (c.ClassicInstaller != null)
                        {
                            Node nc = i.GetChildElement("classic");
                            if (nc != null)
                            {
                                if (!nc.Attributes.Keys.Contains("instversion"))
                                {
                                    if (c.ClassicInstaller.InstalledVersion == -1)
                                    {
                                        c.ForceInstalledVersion(c.ClassicInstaller, c.ClassicInstaller.AvailableVersion);
                                        nc.Attributes.Add("instversion", c.ClassicInstaller.AvailableVersion.ToString());
                                    }
                                    else
                                    {
                                        c.ForceInstalledVersion(c.ClassicInstaller, c.ClassicInstaller.InstalledVersion);
                                        nc.Attributes.Add("instversion", c.ClassicInstaller.InstalledVersion.ToString());
                                    }
                                }
                                else
                                {
                                    nc.Attributes["instversion"] = c.ClassicInstaller.InstalledVersion.ToString();
                                }

                                // Ensure a copy of the version information is made in the backup.
                                RecoveryUtil.SaveVersionInformationForObject(this.Repository.Url, this.ID, c.ID, "classic", "installer", c.ClassicInstaller.InstalledVersion);
                            }
                        }
                        foreach (Node nc in i.GetChildElements("link"))
                        {
                            foreach (Objects.Link l in c.Links)
                            {
                                // Normalize the link.
                                string linkpath = nc.GetChildElement("local").GetText().Value;
                                if (!linkpath.ToLowerInvariant().EndsWith(".lnk")) linkpath = linkpath + ".lnk";

                                if (l.Local == linkpath)
                                {
                                    if (!nc.Attributes.Keys.Contains("instversion"))
                                    {
                                        c.ForceInstalledVersion(l, l.AvailableVersion);
                                        nc.Attributes.Add("instversion", l.AvailableVersion.ToString());
                                    }
                                    else
                                    {
                                        nc.Attributes["instversion"] = l.InstalledVersion.ToString();
                                    }
                                }
                            }
                        }
                    }
                    catch (KeyNotFoundException)
                    {
                    }
                }
            }
            else // Ensure instversion isn't in there.
            {
                // Calculate which components are installed and maintain references
                // to them.
                Dictionary<string, Component> installed = new Dictionary<string, Component>();
                foreach (Component c in this.p_Components.Values)
                {
                    if (c.IsInstalled())
                        installed.Add(c.ID, c);
                }

                // Search through the component nodes.
                foreach (Node n in this.XmlTree.GetChildElement("application").GetChildElements("component"))
                {
                    try
                    {
                        n.Children.Remove(n.GetChildElement("instversion"));
                        Component c = installed[n.GetChildElement("id").GetText().Value];
                    }
                    catch (KeyNotFoundException)
                    {
                    }
                }
            }

            Cache.UpdateApplicationData(this);
        }

        /// <summary>
        /// A struct that is used to hold temporary installation version data
        /// while SyncNew operates.
        /// </summary>
        private class TemporaryComponent
        {
            internal Dictionary<string, int> FileVersions = new Dictionary<string, int>();
            internal Dictionary<string, int> FolderVersions = new Dictionary<string, int>();
            internal Dictionary<string, int> LinkVersions = new Dictionary<string, int>();
            internal int ClassicVersion = 0;
            internal bool HasClassic = false;
        }

        /// <summary>
        /// Synchronizes new application data into this one, keeping all of the InstalledVersion
        /// information.
        /// </summary>
        /// <param name="a">The Application instance to retrieve all the new data from.</param>
        internal void SyncNew(Application a)
        {
            // We must first store a collection of all the InstalledVersion values.
            Dictionary<string, TemporaryComponent> components = new Dictionary<string, TemporaryComponent>();
            foreach (Component c in this.p_Components.Values)
                if (c.IsInstalled())
                {
                    TemporaryComponent t = new TemporaryComponent();
                    foreach (Objects.File f in c.Files)
                        t.FileVersions.Add(f.Local, f.InstalledVersion);
                    foreach (Objects.Folder f in c.Folders)
                        t.FolderVersions.Add(f.Local, f.InstalledVersion);
                    foreach (Objects.Link f in c.Links)
                        t.LinkVersions.Add(f.Local, f.InstalledVersion);
                    if (c.ClassicInstaller != null)
                    {
                        t.ClassicVersion = c.ClassicInstaller.InstalledVersion;
                        t.HasClassic = true;
                    }
                    components.Add(c.ID, t);
                }

            this.p_Category = a.p_Category;
            this.p_Components = a.p_Components;
            this.p_Description = a.p_Description;
            this.p_ID = a.p_ID;
            this.p_Image = a.p_Image;
            this.p_ImageSource = a.p_ImageSource;
            this.p_InstallationData = a.p_InstallationData;
            this.p_IsAvailable = a.p_IsAvailable;
            this.p_IsValid = a.p_IsValid;
            this.p_Name = a.p_Name;
            this.p_PermitModification = a.p_PermitModification;
            this.p_Repository = a.p_Repository;
            this.p_XmlSource = a.p_XmlSource;
            this.p_XmlTree = a.p_XmlTree;

            // Now re-apply the installed versions to the components.
            foreach (KeyValuePair<string, TemporaryComponent> oc in components)
            {
                Component c = this.p_Components[oc.Key];
                Node instnode = null;
                try
                {
                    foreach (Node n in this.XmlTree.GetChildElement("application").GetChildElements("component"))
                        if (n.GetChildElement("id").GetText().Value == oc.Key)
                        {
                            instnode = n.GetChildElement("installation");
                            break;
                        }
                }
                catch (KeyNotFoundException)
                {
                    // TODO: What to do here?  We have no way of marking
                    // files for deletion, but we've already updated the
                    // application data at this point!
                }

                foreach (KeyValuePair<string, int> kv in oc.Value.FileVersions)
                {
                    bool deleted = true;
                    foreach (Objects.File f in c.Files)
                        if (f.Local == kv.Key)
                        {
                            f.InstalledVersion = kv.Value;
                            f.DeleteOnNextUpdate = false;
                            deleted = false;
                            break;
                        }

                    // Handle whether or not the file should be deleted
                    // when the application next updates.
                    if (deleted)
                    {
                        Objects.File f = new Objects.File(kv.Key, null, true);
                        f.Variables = c.Variables;
                        f.DeleteOnNextUpdate = true;
                        c.Files.Add(f);

                        // Add to the XmlTree.
                        Node fn = new Node(instnode, "element", "file", null);
                        fn.Attributes.Add("deleted", "true");
                        Node dn = new Node(fn, "element", "local", null);
                        dn.Children.Add(new Node(dn, "text", null, kv.Key));
                        fn.Children.Add(dn);
                        instnode.Children.Add(fn);
                    }
                }
                foreach (KeyValuePair<string, int> kv in oc.Value.FolderVersions)
                {
                    bool deleted = true;
                    foreach (Objects.Folder f in c.Folders)
                        if (f.Local == kv.Key)
                        {
                            f.InstalledVersion = kv.Value;
                            f.DeleteOnNextUpdate = false;
                            deleted = false;
                            break;
                        }

                    // Handle whether or not the folder should be deleted
                    // when the application next updates.
                    if (deleted)
                    {
                        Objects.Folder f = new Objects.Folder(kv.Key);
                        f.Variables = c.Variables;
                        f.DeleteOnNextUpdate = true;
                        c.Folders.Add(f);

                        // Add to the XmlTree.
                        Node fn = new Node(instnode, "element", "folder", null);
                        fn.Attributes.Add("deleted", "true");
                        Node dn = new Node(fn, "element", "local", null);
                        dn.Children.Add(new Node(dn, "text", null, kv.Key));
                        fn.Children.Add(dn);
                        instnode.Children.Add(fn);
                    }
                }
                foreach (KeyValuePair<string, int> kv in oc.Value.LinkVersions)
                {
                    bool deleted = true;
                    foreach (Objects.Link f in c.Links)
                        if (f.Local == kv.Key)
                        {
                            f.InstalledVersion = kv.Value;
                            f.DeleteOnNextUpdate = false;
                            deleted = false;
                            break;
                        }

                    // Handle whether or not the link should be deleted
                    // when the application next updates.
                    if (deleted)
                    {
                        Objects.Link f = new Objects.Link(kv.Key, null);
                        f.Variables = c.Variables;
                        f.DeleteOnNextUpdate = true;
                        c.Links.Add(f);

                        // Add to the XmlTree.
                        Node fn = new Node(instnode, "element", "link", null);
                        fn.Attributes.Add("deleted", "true");
                        Node dn = new Node(fn, "element", "local", null);
                        dn.Children.Add(new Node(dn, "text", null, kv.Key));
                        fn.Children.Add(dn);
                        instnode.Children.Add(fn);
                    }
                }

                // Handle versioning on classic installers.
                if (oc.Value.HasClassic && c.ClassicInstaller != null)
                {
                    c.ClassicInstaller.InstalledVersion = oc.Value.ClassicVersion;
                }
            }

            // Now sync the application back to the cache.
            this.SyncCache();
        }

        /// <summary>
        /// Begins an asynchronous installation process of a component.
        /// </summary>
        /// <param name="id">The ID of the component to install.</param>
        /// <param name="onComplete">The callback to execute when the installation finishes.</param>
        /// <param name="onFailed">The callback to execute when the installation fails.</param>
        /// <param name="onProgress">The callback to execute when the installation progresses.</param>
        internal void Install(string id, Installation.InstallationProgressHandler onProgress, Installation.InstallationCompleteHandler onComplete, Installation.InstallationFailedHandler onFailed)
        {
            if (!this.p_Components.Keys.Contains(id))
                throw new ArgumentException();

            this.Install(this.p_Components[id], onProgress, onComplete, onFailed, null);
        }

        /// <summary>
        /// Begins an asynchronous installation process of a component.
        /// </summary>
        /// <param name="component">The component to install.</param>
        /// <param name="onComplete">The callback to execute when the installation finishes.</param>
        /// <param name="onFailed">The callback to execute when the installation fails.</param>
        /// <param name="onProgress">The callback to execute when the installation progresses.</param>
        internal void Install(Component component, Installation.InstallationProgressHandler onProgress, Installation.InstallationCompleteHandler onComplete, Installation.InstallationFailedHandler onFailed, IconAnimator animator)
        {
            if (!this.p_Components.Values.Contains(component))
                throw new ArgumentException();

            this.p_InstallationData = new Installation(component, this, Installation.InstallMode.INSTALL);
            if (onProgress != null)
                this.p_InstallationData.InstallationProgress += onProgress;
            if (onComplete != null)
                this.p_InstallationData.InstallationComplete += onComplete;
            if (onFailed != null)
                this.p_InstallationData.InstallationFailed += onFailed;
            if (animator != null)
                this.p_InstallationData.Animator = animator;
            this.p_InstallationData.Start();
        }

        /// <summary>
        /// Begins an asynchronous uninstallation process of a component.
        /// </summary>
        /// <param name="component">The component to uninstall.</param>
        /// <param name="onComplete">The callback to execute when the uninstallation finishes.</param>
        /// <param name="onFailed">The callback to execute when the uninstallation fails.</param>
        /// <param name="onProgress">The callback to execute when the uninstallation progresses.</param>
        internal void Uninstall(Component component, Installation.InstallationProgressHandler onProgress, Installation.InstallationCompleteHandler onComplete, Installation.InstallationFailedHandler onFailed, IconAnimator animator)
        {
            if (!this.p_Components.Values.Contains(component))
                throw new ArgumentException();

            this.p_InstallationData = new Installation(component, this, Installation.InstallMode.UNINSTALL);
            if (onProgress != null)
                this.p_InstallationData.InstallationProgress += onProgress;
            if (onComplete != null)
                this.p_InstallationData.InstallationComplete += onComplete;
            if (onFailed != null)
                this.p_InstallationData.InstallationFailed += onFailed;
            if (animator != null)
                this.p_InstallationData.Animator = animator;
            this.p_InstallationData.Start();
        }

        /// <summary>
        /// Begins an asynchronous update process of a component.
        /// </summary>
        /// <param name="component">The component to update.</param>
        /// <param name="onComplete">The callback to execute when the update finishes.</param>
        /// <param name="onFailed">The callback to execute when the update fails.</param>
        /// <param name="onProgress">The callback to execute when the update progresses.</param>
        internal void Update(Component component, Installation.InstallationProgressHandler onProgress, Installation.InstallationCompleteHandler onComplete, Installation.InstallationFailedHandler onFailed, IconAnimator animator)
        {
            if (!this.p_Components.Values.Contains(component))
                throw new ArgumentException();

            this.p_InstallationData = new Installation(component, this, Installation.InstallMode.UPDATE);
            if (onProgress != null)
                this.p_InstallationData.InstallationProgress += onProgress;
            if (onComplete != null)
                this.p_InstallationData.InstallationComplete += onComplete;
            if (onFailed != null)
                this.p_InstallationData.InstallationFailed += onFailed;
            if (animator != null)
                this.p_InstallationData.Animator = animator;
            this.p_InstallationData.Start();
        }

        /// <summary>
        /// Returns whether or not one or more of the application components is currently installed.
        /// </summary>
        /// <returns>Whether or not one or more of the application components is currently installed.</returns>
        internal bool IsInstalled()
        {
            foreach (Component c in this.p_Components.Values)
            {
                if (c.IsInstalled())
                    return true;
            }
            return false;
        }

        /// <summary>
        /// The ID of the application within the repository.
        /// </summary>
        internal string ID
        {
            get
            {
                return this.p_ID;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_ID = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The URL of the repository which contains this application.
        /// </summary>
        internal Repository Repository
        {
            get
            {
                return this.p_Repository;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_Repository = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The name of the application.
        /// </summary>
        internal string Name
        {
            get
            {
                return this.p_Name;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_Name = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The absolute URL to the source XML document which describes this application.
        /// </summary>
        internal string XmlSource
        {
            get
            {
                return this.p_XmlSource;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_XmlSource = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The absolute URL to the source XML document which describes this application.
        /// </summary>
        internal string Url
        {
            get
            {
                return this.XmlSource;
            }
            set
            {
                this.XmlSource = value;
            }
        }

        /// <summary>
        /// The full HTML description of the application.
        /// </summary>
        internal string Description
        {
            get
            {
                return this.p_Description;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_Description = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// Whether the application is available for installation on the computer.
        /// </summary>
        internal bool Available
        {
            get
            {
                return this.p_IsAvailable;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_IsAvailable = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// Whether the application is valid.
        /// </summary>
        internal bool Valid
        {
            get
            {
                return this.p_IsValid;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_IsValid = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The category for the application.
        /// </summary>
        internal string Category
        {
            get
            {
                return this.p_Category;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_Category = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The list view image associated with the application.
        /// </summary>
        internal Bitmap Image
        {
            get
            {
                return this.p_Image;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_Image = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The URL to the list view image associated with the application.
        /// </summary>
        internal string ImageSource
        {
            get
            {
                return this.p_ImageSource;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_ImageSource = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// Reloads the list view image from the list view image source.
        /// </summary>
        /// <param name="local">The local path to the cached image.</param>
        internal void ReloadImage(string local)
        {
            this.p_Image = new Bitmap(local);
        }

        /// <summary>
        /// The components available within an application.
        /// </summary>
        internal Dictionary<string, Component> Components
        {
            get
            {
                return this.p_Components;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_Components = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The XML tree associated with this application.
        /// </summary>
        internal Tree XmlTree
        {
            get
            {
                return this.p_XmlTree;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_XmlTree = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// Whether the properties of the application can be modified.  This value can not be
        /// changed after it is set to false.
        /// </summary>
        internal bool PermitModification
        {
            get
            {
                return this.p_PermitModification;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_PermitModification = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The installation data currently associated with the application.
        /// </summary>
        internal Installation InstallationData
        {
            get
            {
                return this.p_InstallationData;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_InstallationData = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The local path to the executable to be launched (if this application supports launching).
        /// </summary>
        internal string LaunchPath
        {
            get
            {
                return this.p_LaunchPath;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_LaunchPath = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// The arguments to be passed to the local executable to be launched (if this application supports launching).
        /// </summary>
        internal string LaunchArgs
        {
            get
            {
                return this.p_LaunchArgs;
            }
            set
            {
                if (this.p_PermitModification)
                    this.p_LaunchArgs = value;
                else
                    throw new ApplicationModificationNotPermittedException();
            }
        }

        /// <summary>
        /// Whether this application is launchable.
        /// </summary>
        internal bool Launchable
        {
            get
            {
                return (this.p_LaunchPath != null && this.p_LaunchArgs != null);
            }
        }

        public override object InitializeLifetimeService()
        {
            return null;
        }
    }

    public class ApplicationModificationNotPermittedException : ApplicationException
    {
        public ApplicationModificationNotPermittedException() : base() { }
        public ApplicationModificationNotPermittedException(string message) : base(message) { }
        public ApplicationModificationNotPermittedException(string message, System.Exception inner) : base(message, inner) { }
    }
}
